package org.gap.wizards.creature;

import java.awt.Point;

import org.gap.wizards.creature.Creature.Attribute;
import org.gap.wizards.pc.PlayerCharacter;
import org.gap.wizards.utils.AStar;
import org.gap.wizards.utils.Dice;
import org.gap.wizards.utils.DiceBag;
import org.gap.wizards.utils.Node;
import org.gap.wizards.utils.Persistence;
import org.gap.wizards.world.Location;
import org.gap.wizards.world.Movement;

public class AnimalBrain implements Brain {
	private static final String PERSONALITY = "personality";
	private static final String AGGRESSIVE = "aggressive";

	public static final int NW = 1, N = 2, NE = 3, W = 4, E = 5, SW = 6, S = 7, SE = 8;

	private final DiceBag dice;
	private boolean isAggressive = false;
	private Creature creature;
	private boolean isSkittish;

	public AnimalBrain(DiceBag dice) {
		this.dice = dice;
	}

	@Override
	public void setCreature(Creature creature) {
		this.creature = creature;
	}

	@Override
	public void update(Location location) {
		if (isWounded() || (isAggressive && isCloseToPlayer(location))) {
			attackPlayer(location);
		} else if (!isSkittish && (isCloseToPlayer(location) || dice.roll(Dice.D4, 1) > 2)) {
			moveToPlayer(location);
		} else if (isSkittish && isCloseToPlayer(location)) {
			moveAwayFromPlayer(location);
		} else {
			wander(location);
		}
	}

	private boolean isWounded() {
		return creature.getAttribute(Attribute.Hitpoints) < creature.getMaxAttribute(Attribute.Hitpoints);
	}
	
	private void attackPlayer(Location location) {
		Node path = getPathToPlayer(location);
		if (path == null) {
			// creature should rest and recover.
		} else {
			PlayerCharacter pc = location.getPlayerCharacter();
			if (path.distance() > 10) {
				creature.setLocation(path.position());
			} else {
				creature.attack((Creature)pc);
			}
		}
	}
	
	private void moveToPlayer(Location location) {
		Node path = getPathToPlayer(location);
		if (path == null) {
			wander(location);
		} else {
			Point position = path.position();
			if (location.canMoveTo(position)) {
				creature.setLocation(position);
			}
			return;
		}
	}

	private Node getPathToPlayer(Location location) {
		AStar pathFinder = new AStar();
		pathFinder.setLocation(location);
		Point creatureLocation = creature.getLocation();
		return pathFinder.findPath(creatureLocation, location.getCharacterLocation());
	}

	private boolean isCloseToPlayer(Location location) {
		Point creatureLocation = creature.getLocation();
		Point characterLocation = location.getCharacterLocation();
		return creatureLocation.distance(characterLocation) < 5;
	}
	
	private void moveAwayFromPlayer(Location location) {
		Point creatureLocation = creature.getLocation();
		Point playerLocation = location.getCharacterLocation();
		int x = (creatureLocation.x > playerLocation.x? 1: -1);
		int y = (creatureLocation.y > playerLocation.y? 1: -1);
		if (canGoTo(location, creatureLocation, x, y)) {
			translatePoint(creatureLocation, x, y);
		} else if (canGoTo(location, creatureLocation, x = -x, y)) {
			translatePoint(creatureLocation, x, y);
		} else if (canGoTo(location, creatureLocation, x = -x, y = -y)) {
			translatePoint(creatureLocation, x, y);
		}
	}

	private void translatePoint(Point creatureLocation, int x, int y) {
		creatureLocation.translate(x, y);
		creature.setLocation(creatureLocation);
	}

	private boolean canGoTo(Location location, Point creatureLocation, int x, int y) {
		Point runAwayDirection = new Point(x, y);
		Point runAwayPoint = new Point(creatureLocation.x, creatureLocation.y);
		runAwayPoint.translate(runAwayDirection.x, runAwayDirection.y);
		return location.canMoveTo(runAwayPoint);
	}
	
	private void wander(Location location) {
		Point creatureLocation = creature.getLocation();
		Point point = null;
		boolean cantMove = true;
		while (cantMove) {
			point = creatureLocation;
			point = getWanderDirection(point);
			cantMove = !location.canMoveTo(point);
		}
		creature.setLocation(point);
	}

	private Point getWanderDirection(Point point) {
		Movement move = null;
		switch (dice.roll(Dice.D8, 1)) {
			case NW:
				move = Movement.UP_LEFT;
				break;
			case N:
				move = Movement.UP;
				break;
			case NE:
				move = Movement.UP_RIGHT;
				break;
			case W:
				move = Movement.LEFT;
				break;
			case E:
				move = Movement.RIGHT;
				break;
			case SW:
				move = Movement.DOWN_LEFT;
				break;
			case S:
				move = Movement.DOWN;
				break;
			case SE:
				move = Movement.DOWN_RIGHT;
				break;
		}
		return move.adjustLocation(point);
	}
	
	public void setAggressive(boolean aggressive) {
		isAggressive = true;
	}
	
	@Override
	public void setSkittish(boolean skittish) {
		this.isSkittish = skittish;
	}

	@Override
	public boolean equals(Object obj) {
		if (obj != null && obj instanceof AnimalBrain) {
			AnimalBrain other = (AnimalBrain)obj;
			return isAggressive == other.isAggressive;
		}
		return false;
	}

	@Override
	public void persist(Persistence persister) {
		persister.beginSection(PERSONALITY);
		persister.set(AGGRESSIVE, isAggressive);
		persister.endSection();
	}

	@Override
	public void load(Persistence persister) {
		persister.beginSection(PERSONALITY);
		persister.getBoolean(AGGRESSIVE);
		persister.endSection();
	}
}
